package org.luxor.commons.playwright.component;

import cn.hutool.core.exceptions.ExceptionUtil;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.options.ScreenshotType;
import org.apache.commons.codec.binary.Base64;
import org.luxor.commons.core.utils.StrKit;
import org.luxor.commons.core.utils.UuidUtils;
import org.luxor.commons.playwright.component.command.PlaywrightCommand;
import org.luxor.commons.playwright.config.properties.PlaywrightProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.helpers.MessageFormatter;
import org.springframework.boot.logging.LogLevel;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 请求上下文
 *
 * @author Mr.yan  @date 2022/6/9
 */
public class Context extends HashMap {
    private Logger log = LoggerFactory.getLogger(Context.class);

    private String trackId;
    private String sessionId;
    private AtomicInteger executeCount;
    private PlaywrightProperties properties;

    private ArrayList<TrackLog> trackLogs;
    private ExecState state;
    private Date startTime;
    private Date endTime;

    public Context() {
        super(8);
        this.trackId = StrKit.EMPTY;
        this.sessionId = StrKit.EMPTY;
        this.executeCount = new AtomicInteger(1);
        this.trackLogs = new ArrayList<>();
    }

    /**
     * 初始化
     *
     * @param properties 全局配置
     * @return void
     */
    public void execInitialize(PlaywrightProperties properties, PlaywrightCommand<?> command) {
        this.log = LoggerFactory.getLogger(command.getClass());
        this.properties = properties;
        this.state = ExecState.INITIALIZE;
        if (this.executeCount.get() == 1) {
            this.startTime = new Date();
        }
        if (!StringUtils.hasText(this.getTrackId())) {
            this.trackId = UuidUtils.uuid32();
        }
        if (!StringUtils.hasText(this.getSessionId())) {
            this.sessionId = UuidUtils.uuid();
        }
        this.info("初始化.");
    }

    /**
     * 开始执行
     *
     * @return void
     */
    public void execStart() {
        this.state = ExecState.RUNNING;
        this.info("开始执行.");
    }

    /**
     * 暂停执行
     *
     * @return void
     */
    public void execSuspend(Page page) {
        this.endTime = new Date();
        this.state = ExecState.SUSPEND;
        this.info(page, "暂停执行.");
    }

    /**
     * 执行完成
     *
     * @return void
     */
    public void execCompleted() {
        this.endTime = new Date();
        if (ExecState.RUNNING == this.state || ExecState.INITIALIZE == this.state) {
            this.state = ExecState.COMPLETE;
        }
        this.info("执行完成. ^.^ ");
    }

    /**
     * 执行失败
     *
     * @return void
     */
    public void execFailed(Page page, Throwable e) {
        this.endTime = new Date();
        this.state = ExecState.FAILURE;
        this.error(page, "执行失败", e);
    }


    /**
     * 创建快照
     *
     * @param page 当前页
     * @return org.luxor.commons.playwright.component.Snapshot
     */
    public Screenshot createScreenshot(Page page) {
        Screenshot screenshot = new Screenshot();
        // 创建网页快照
        byte[] image = page.screenshot(new Page.ScreenshotOptions()
                .setType(ScreenshotType.PNG)
                .setFullPage(true)
        );
        screenshot.setBase64Image(Base64.encodeBase64String(image));
        // 保存网页源码
        screenshot.setHtmlText(page.content());
        return screenshot;
    }

    /**
     * 添加追踪轨迹记录
     *
     * @param logLevel   日志级别
     * @param screenshot 界面快照
     * @param format     日志格式
     * @param params     日志参数
     * @return void
     */
    private void addTrack(LogLevel logLevel, Screenshot screenshot, String format, Object... params) {
        switch (logLevel) {
            case TRACE:
                log.trace(String.format("TrackId:[%s](%s) " + format, this.trackId, this.executeCount.get()), params);
                break;
            case DEBUG:
                log.debug(String.format("TrackId:[%s](%s) " + format, this.trackId, this.executeCount.get()), params);
                break;
            case INFO:
                log.info(String.format("TrackId:[%s](%s) " + format, this.trackId, this.executeCount.get()), params);
                break;
            case WARN:
                log.warn(String.format("TrackId:[%s](%s) " + format, this.trackId, this.executeCount.get()), params);
                break;
            case ERROR:
                log.error(String.format("TrackId:[%s](%s) " + format, this.trackId, this.executeCount.get()), params);
                break;
        }
        TrackLog trackLog = new TrackLog();
        if (screenshot != null) {
            trackLog.setBase64Image(screenshot.getBase64Image());
            trackLog.setHtmlText(screenshot.getHtmlText());
        }
        String message = MessageFormatter.arrayFormat(String.format("(%s) " + format, this.executeCount.get()), params).getMessage();
        trackLog.setMessage(message);
        this.trackLogs.add(trackLog);
    }

    /**
     * 添加追踪轨迹异常记录
     *
     * @param screenshot 界面快照
     * @param message    日志信息
     * @param e          异常堆栈
     * @return void
     */
    private void addTrack(Screenshot screenshot, String message, Throwable e) {
        log.error(String.format("TrackId:[%s](%s) " + message, this.trackId, this.executeCount.get()), e);
        TrackLog trackLog = new TrackLog();
        if (screenshot != null) {
            trackLog.setBase64Image(screenshot.getBase64Image());
            trackLog.setHtmlText(screenshot.getHtmlText());
        }
        trackLog.setMessage(String.format("(%s) " + message, this.executeCount.get()) + ":\r\n" + ExceptionUtil.stacktraceToString(e));
        this.trackLogs.add(trackLog);
    }

    public void trace(String format, Object... params) {
        this.trace(null, format, params);
    }

    public void trace(Page page, String format, Object... params) {
        if (page != null) {
            this.addTrack(LogLevel.TRACE, createScreenshot(page), format, params);
        } else {
            this.addTrack(LogLevel.TRACE, null, format, params);
        }
    }

    public void debug(String format, Object... params) {
        this.debug(null, format, params);
    }

    public void debug(Page page, String format, Object... params) {
        if (page != null) {
            this.addTrack(LogLevel.DEBUG, createScreenshot(page), format, params);
        } else {
            this.addTrack(LogLevel.DEBUG, null, format, params);
        }
    }

    public void info(String format, Object... params) {
        this.info(null, format, params);
    }

    public void info(Page page, String format, Object... params) {
        if (page != null) {
            this.addTrack(LogLevel.INFO, this.createScreenshot(page), format, params);
        } else {
            this.addTrack(LogLevel.INFO, null, format, params);
        }
    }

    public void warn(String format, Object... params) {
        this.warn(null, format, params);
    }

    public void warn(Page page, String format, Object... params) {
        if (page != null) {
            this.addTrack(LogLevel.WARN, this.createScreenshot(page), format, params);
        } else {
            this.addTrack(LogLevel.WARN, null, format, params);
        }
    }

    public void error(String format, Object... params) {
        this.error(format, params);
    }

    public void error(Page page, String format, Object... params) {
        if (page != null) {
            this.addTrack(LogLevel.ERROR, this.createScreenshot(page), format, params);
        } else {
            this.addTrack(LogLevel.ERROR, null, format, params);
        }
    }

    public void error(String message, Throwable e) {
        this.error(null, message, e);
    }

    public void error(Page page, String message, Throwable e) {
        if (page != null) {
            this.addTrack(this.createScreenshot(page), message, e);
        } else {
            this.addTrack(null, message, e);
        }
    }


    /**
     * 获取轨迹跟踪唯一ID
     *
     * @return java.lang.String
     */
    public String getTrackId() {
        return this.trackId;
    }

    public void setTrackId(String trackId) {
        this.trackId = trackId;
    }

    /**
     * 浏览器SessionId
     *
     * @return java.lang.String
     */
    public String getSessionId() {
        return this.sessionId;
    }

    public void setSessionId(String sessionId) {
        this.sessionId = sessionId;
    }

    /**
     * 全局配置
     *
     * @return org.luxor.commons.playwright.config.properties.playwrightProperties
     */
    public PlaywrightProperties getConfig() {
        return this.properties;
    }

    /**
     * 执行状态
     *
     * @return org.luxor.commons.playwright.component.ExecState
     */
    public ExecState getState() {
        return state;
    }

    /**
     * 开始时间
     *
     * @return java.util.Date
     */
    public Date getStartTime() {
        return startTime;
    }

    /**
     * 结束时间
     *
     * @return java.util.Date
     */
    public Date getEndTime() {
        return endTime;
    }

    /**
     * 当前执行次数
     *
     * @return int
     */
    public int getExecuteCount() {
        return executeCount.get();
    }

    /**
     * 执行次数+1
     *
     * @return int
     */
    public void incrementAndExecuteCount() {
        this.executeCount.incrementAndGet();
    }

    /**
     * 轨迹记录列表
     *
     * @return java.util.ArrayList<org.luxor.commons.playwright.component.TrackLog>
     */
    public ArrayList<TrackLog> getTrackLogs() {
        return this.trackLogs;
    }

}
